package edu.wpi.cs509.ui.simulators;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Timer;

import edu.wpi.cs509.clientlogic.UserSession;
import edu.wpi.cs509.common.exceptions.ServerUnreachable;
import edu.wpi.cs509.ui.intfc.ClientLogicRequest;
import edu.wpi.cs509.ui.intfc.GameEventNotify;
import edu.wpi.cs509.ui.intfc.ClientLogicNotify;

/**
 * The client logic simulator provides a simulated implementation of the client/server
 * logic in order to allow the UI to be independently developed.  It implements
 * the ClientLogicRequest interface, and utilizes a timer delay to simulate the
 * asynchronous server delay.
 * 
 * @author afoltan
 *
 */
public class ClientLogicSimulator implements ClientLogicRequest {
		
	private static final Random R = new Random();
	
	private final int SERVER_DELAY = 100;					// msec
	private final int SERVER_DELAY_VARIABILITY = 200;		// msec
	
	// the ClientLogic notification handlers
	private ClientLogicNotify ui;	
	
	List<ServerGame> games = new ArrayList<ServerGame>();

	/**
	 * Constructor for the simulator
	 * @param ui - the UI handlers for client event notifications
	 */
	public ClientLogicSimulator( ClientLogicNotify ui ) {
		System.out.println("--- Client Logic SIMULATOR ---");
		this.ui = ui;
		
		// pre-populate the games lists
		int count = R.nextInt(10) + 1;
		ServerGame sGame;
		Opponent opp;
		AI ai;
		for (int i=0; i<count; i++) {
			sGame = new ServerGame();
			ai = new AI();
			opp = new Opponent( ai );
			sGame.joinAsPlayer1(opp.getUserSession(), opp.getGameNotify());
			opp.getGameNotify().setGameInterface( sGame.getPlayer1() );
			games.add( sGame );
			
			new Timer().schedule( new NewGameNotify(opp.getClientLogicNotify(),sGame.getPlayer1()), delay() );
		}

	}

	/**
	 * Delay provides a randomized time delay in msec that can
	 * be used by the timer.schedule() method to simulate round-trip
	 * messaging delays to the server.
	 * @return
	 */
	private int delay() {
		int d;
		
		// server delay = minimum + rand(0..variability) = 500 to 1500 msec
		d = R.nextInt(SERVER_DELAY_VARIABILITY) + SERVER_DELAY;
		return d;
	}
	
	/**
	 * Trace support
	 */
	private void trace(String func) {
		System.out.println("UI ----- "+func+" -----> ClientLogic");
	}

	/*
	 * The following methods are part of the ClientLogicRequest Interface
	 */
	
	/**
	 * Log in request.  After a delay, an asynchronous response with the success
	 * status will be sent.  No authentication is performed.
	 * @param username - the username
	 * @param password - the user password
	 * @param host - IP address / hostname of the server
	 */
	@Override
	public void login(String username, String password, String host)
			throws ServerUnreachable {
		
		trace("login()");
		
		UserSessionSim session = new UserSessionSim( username );
		new Timer().schedule( new LoginSuccessNotify(ui,session), delay() );
	}

	/**
	 * Log out the current user.  There is no notification message for this.
	 */
	@Override
	public void logout(UserSession session) {
		trace("logout()");

		UserSessionSim simSession = (UserSessionSim)session;
		System.out.println("User "+simSession.getUsername()+" has logged out");
		// nothing to do -- no notification to generate
	}


	/**
	 * Get the leaderboard starting at 'rank' and including the next 'count'
	 * people.  For example getLeaderBoard(1,10) will fetch the top ten list.
	 * 
	 * After a delay, an asynchronous response with the leaderboard list 
	 * will be sent.
	 * 
	 * @param rank  - the starting rank player to fetch
	 * @param count - the number of players (including rank) to fetch
	 */
	@Override
	public void requestLeaderBoard(int rank, int count) throws ServerUnreachable {
		trace("getLeaderBoard(rank)");
		new Timer().schedule( new LeaderboardNotfiy(ui,rank,count), delay() );
	}

	/**
	 * Get the leaderboard for a specific user 'username' and include the next 'count'
	 * people.  For example getLeaderBoard("joe",10) will return joe's rank and the 
	 * 9 additional people after joe.
	 * 
	 * After a delay, an asynchronous response with the leaderboard list 
	 * will be sent.
	 * 
	 * @param username - the name of the first user to locate
	 * @param count - the number of players (including rank) to fetch
	 */
	@Override
	public void requestLeaderBoard(String username, int count)
			throws ServerUnreachable {
		trace("getLeaderboard(username)");
		new Timer().schedule( new LeaderboardNotfiy(ui,username,count), delay() );
	}

	/**
	 * Gets the personal stats for the named user.
	 * 
	 * After a delay, an asynchronous response with the player stats 
	 * will be sent.
	 * 
	 * @param username - the name of the user to retrieve stats
	 */
	@Override
	public void requestPersonalStats(String username) throws ServerUnreachable {
		trace("getPersonalStats()");
		new Timer().schedule( new PlayerStatsNotfiy(ui,username), delay() );
	}

	/**
	 * Retrieves the list of games.
	 * 
	 * After a delay, an asynchronous response with the list of games 
	 * will be sent.
	 * 
	 */
	@Override
	public void requestGames() throws ServerUnreachable {
		trace("requestGames()");
		new Timer().schedule( new GamesNotify(ui, this), delay() );
	}

	/**
	 * Start a new game on the server
	 * 
	 * After a delay, an asynchronous response with the new game 
	 * will be sent.
	 * 
	 * @param session - the user session for this login
	 * @param notify - the handler for game events
	 */
	@Override
	public void startNewGame(UserSession session, GameEventNotify notify)
			throws ServerUnreachable {
		trace("startNewGame()");
		
		if (session instanceof UserSessionSim) {
			UserSessionSim simSession = (UserSessionSim)session;
		
			ServerGame sGame = new ServerGame();
			ClientGame cGame = sGame.joinAsPlayer1( simSession, notify );
			
			new Timer().schedule( new NewGameNotify(ui,cGame), delay() );
		} else {
			System.out.println("startNewGame: unknown UserSession");
		}
	}

	/**
	 * Join a game on the server
	 * 
	 * After a delay, an asynchronous response with the game will
	 * be sent.
	 * 
	 * @param session - the user session for this login
	 * @paramID - the gameId of the game to join
	 * @param notify - the handler for game events
	 */
	@Override
	public void joinGame(UserSession session, long gameID,
			GameEventNotify notify) throws ServerUnreachable {
		trace("joinGame()");
		UserSessionSim simSession = (UserSessionSim)session;
		
		ServerGame sGame = findGame(gameID);

		if (sGame == null) {
			System.out.println("joinGame: game "+gameID+" not found");
			return;
		}
		
		ClientGame cGame = sGame.joinAsPlayer2( simSession, notify );
		new Timer().schedule( new GameJoinedNotfiy(ui,cGame), delay() );
	}
	
	
	/**
	 * Locate a game by gameID
	 * @param gameID
	 * @return
	 */
	private ServerGame findGame(long gameID) {
		
		for (ServerGame g : games) {
			if (g.getGameID() == gameID) {
				return g;
			}
		}
		
		return null;
	}

	/**
	 * Observe a game on the server
	 * 
	 * After a delay, an asynchronous response with the game will
	 * be sent.
	 * 
	 * @param session - the user session for this login
	 * @paramID - the gameId of the game to join
	 * @param notify - the handler for game events
	 */
	@Override
	public void spectateStart(UserSession session, long gameID,
			GameEventNotify notify) throws ServerUnreachable {
		trace("spectateStart()");
		UserSessionSim simSession = (UserSessionSim)session;
		
		ServerGame sGame = findGame(gameID);

		if (sGame == null) {
			System.out.println("joinGame: game "+gameID+" not found");
			return;
		}
		
		ClientGame cGame = sGame.joinAsPlayer2( simSession, notify );
		new Timer().schedule( new GameObservedNotfiy(ui,cGame), delay() );
	}

	/**
	 * Retrieves the rules of Stratego
	 * 
	 * After a delay, an asynchronous response with the rules 
	 * will be sent.
	 * 
	 */
	@Override
	public void requestStrategoRules() throws ServerUnreachable {
		trace("getStrategoRules()");
		new Timer().schedule( new RulesNotify(ui), delay() );
	}
	
	public List<ServerGame> getAllGames() {
		return games;
	}
}
